package cn.edu.zut.sunday.blimp.controller;

import cn.edu.zut.sunday.base.controller.GenericController;
import cn.edu.zut.sunday.blimp.domain.DatasetName;
import cn.edu.zut.sunday.blimp.domain.ExampleInfo;
import cn.edu.zut.sunday.blimp.domain.constant.TableHeader;
import cn.edu.zut.sunday.blimp.func.ExampleInfoTransformUtils;
import cn.edu.zut.sunday.blimp.service.DatasetNameService;
import cn.edu.zut.sunday.blimp.service.ExampleInfoService;
import cn.edu.zut.sunday.blimp.vo.ExampleInfoQuery;
import cn.edu.zut.sunday.blimp.vo.ExampleInfoVo;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import java.util.ArrayList;
import java.util.HashMap;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@Api("blimp-样本信息表接口")
@RestController
@CrossOrigin
@RequestMapping("/blimp/example")
public class ExampleInfoController extends
  GenericController<ExampleInfo, Integer, ExampleInfoService> {

    private DatasetNameService datasetNameServiceImpl;

    /**
     * 创建exampleInfoService对象
     *
     * @param exampleInfoService 由IOC自动注入服务对象
     */
    @Autowired
    public void setExampleInfoService(ExampleInfoService exampleInfoService) {
        this.service = exampleInfoService;
    }

    @Autowired
    public void setDatasetNameService(DatasetNameService datasetNameServiceImpl) {
        this.datasetNameServiceImpl = datasetNameServiceImpl;
    }

    @Override
    protected ExampleInfo addModelCheck(ExampleInfo model,
      HttpServletRequest request) {
        log.info("addModelCheck ExampleInfo: {}", model);
        final boolean success = dealBelongIdInfo(model, request, true);
        if (!success) {return null;}
        return model;
    }

    @Override
    protected ExampleInfo updateModelCheck(Integer id, ExampleInfo model,
      HttpServletRequest request) {
        if (id <= 0) {return null;}
        // 获取样本id信息
        model.setId(id);
        log.info("updateModelCheck ExampleInfo: {}", model);
        final boolean success = dealBelongIdInfo(model, request, false);
        if (!success) {return null;}
        return model;
    }

    /**
     * 对前端传送的高级搜索进行配置，主要是提供belongId搜索，通过该父ID进行查询子列表信息
     *
     * @param requestBodyStr 请求的RequestBody字符串对象，需要之类使用序列化工具创建对应对象
     * @return 返回Specification对象，为查询提供参数，该方法由getList方法自动调用
     */
    @Override
    protected Specification<ExampleInfo> createSpecification(String requestBodyStr) {
        final ExampleInfoQuery infoQuery = new Gson().fromJson(requestBodyStr,
          ExampleInfoQuery.class);
        final ArrayList<Predicate> list = Lists.newArrayList();

        return (root, query, cb) -> {
            if (StringUtils.isNotEmpty(infoQuery.getField())) {
                list.add(cb.like(root.get("field"), sqlLike(infoQuery.getField())));
            }
            if (StringUtils.isNotEmpty(infoQuery.getUID())) {
                list.add(cb.like(root.get("UID"), sqlLike(infoQuery.getUID())));
            }
            if (StringUtils.isNotEmpty(infoQuery.getSentenceGood())) {
                list.add(cb.like(root.get("sentenceGood"), sqlLike(infoQuery.getSentenceGood())));
            }
            if (StringUtils.isNotEmpty(infoQuery.getSentenceBad())) {
                list.add(cb.like(root.get("sentenceBad"), sqlLike(infoQuery.getSentenceBad())));
            }
            return cb.and(list.toArray(new Predicate[0]));
        };
    }

    /**
     * 该方法是对从数据库中查询出来的分页数据进行VO封装
     *
     * @param resultPage 从数据库中获取的数据库实体信息
     * @return 封装了表头和分页信息的Map对象
     */
    @Override
    protected Object transform(Page<ExampleInfo> resultPage) {
        final Page<ExampleInfoVo> exampleInfoVoPage = resultPage.map(
          ExampleInfoTransformUtils.ENTITY_2_VO);
        final HashMap<String, Object> map = new HashMap<>();
        map.put("page", exampleInfoVoPage);
        map.put("header", TableHeader.EXAMPLE_INFO.getHeader());
        return map;
    }

    /**
     * 该方法对从数据库中查出来的实体进行VO封装
     *
     * @param entity 从数据库中获取的一个数据库实体信息
     * @return 封装后的数据对象
     */
    @Override
    protected Object transform(ExampleInfo entity) {
        return ExampleInfoTransformUtils.ENTITY_2_VO.apply(entity);
    }

    /**
     * 从http请求中读取belongId信息，然后根据Id信息查询具体的数据集实体并设置到样本中，这一步是为了建立外键
     *
     * @param model 样本实体信息
     * @param request http请求
     * @return 如果返回false，则表示belongId存在问题，无法执行add/update操作。
     * 如果返回true，则表示设置ExampleInfo#datasetName属性成功。
     */
    private boolean dealBelongIdInfo(ExampleInfo model, HttpServletRequest request,
      boolean created) {
        // 获取该样本信息所属数据集实体Id
        final String belongIdParam = request.getParameter("belongId");
        if (StringUtils.isEmpty(belongIdParam)) {
            return false;
        }
        final int belongId = Integer.parseInt(belongIdParam);
        log.info("dealBelongIdInfo belongId: {}", belongId);
        // 查询该实体信息并且设置到样本的datasetName属性中
        final DatasetName datasetName = datasetNameServiceImpl.findById(belongId);
        if (datasetName != null) {
            if (!created) {
                // 如果是更新，则获取旧的dataset
                final DatasetName oldDatasetName = this.service.findById(model.getId())
                  .getDatasetName();
                // 如果这次修改了belongId，则对旧dataset#count - 1
                if (oldDatasetName.getId() != belongId) {
                    // 旧的数据集 count - 1
                    oldDatasetName.setDataCount(oldDatasetName.getDataCount() - 1);
                    this.datasetNameServiceImpl.save(oldDatasetName);
                }
            }
            // 新的数据集count + 1
            datasetName.setDataCount(datasetName.getDataCount() + 1);
            this.datasetNameServiceImpl.save(datasetName);

            // model设置新的数据集
            model.setDatasetName(datasetName);
            return true;
        }

        return false;
    }

    /**
     * 这里覆盖父类的delete方法是为了修改dataset#dataCount属性
     *
     * @param id entity id
     */
    @ApiOperation(value = "根据ID信息删除一个数据库中的实体")
    @DeleteMapping(value = "/delete/{id}")
    @Override
    public void delete(@PathVariable Integer id) {
        // 这里删除一个样本信息，所属的dataset#dataCount需要-1
        final ExampleInfo exampleInfo = this.service.findById(id);
        // 修改dataCount属性
        final DatasetName datasetName = exampleInfo.getDatasetName();
        datasetName.setDataCount(datasetName.getDataCount() - 1);
        this.datasetNameServiceImpl.save(datasetName);
        // 删除样本
        this.service.delete(id);
    }

    /**
     * 根据数据集Id获取样本分页数据
     *
     * @param belongId 数据集Id信息
     * @param pageNumber 分页页数
     * @param pageSize 分页个数
     * @return 返回分页数据
     */
    @ApiOperation(value = "根据数据集Id获取样本的分页数据")
    @GetMapping(value = "/exampleListByBelongId/{belongId}")
    public Page<ExampleInfoVo> exampleListByBelongId(@PathVariable Integer belongId,
      @RequestParam(value = "pageNumber", defaultValue = "1") String pageNumber,
      @RequestParam(value = "pageSize", defaultValue = "10") String pageSize) {
        int page = 0;
        if (StringUtils.isNotBlank(pageNumber)) {
            page = Integer.parseInt(pageNumber) - 1;
        }
        int size = 10;
        if (StringUtils.isNotBlank(pageSize)) {
            size = Integer.parseInt(pageSize);
        }

        final Pageable pageable = createPageable(page, size);
        Specification<ExampleInfo> specification = (root, query, cb) -> {
            final Join<Object, Object> datasetNameRoot = root.join("datasetName", JoinType.LEFT);
            final Predicate predicateId = cb.equal(datasetNameRoot.get("id"),
              belongId);
            return cb.and(predicateId);
        };

        final Page<ExampleInfo> exampleInfoPage = this.service.findAll(specification, pageable);
        return exampleInfoPage.map(ExampleInfoTransformUtils.ENTITY_2_VO);
    }
}
